import defaultAPI from './default-api'
import { device } from 'mx-libs'
import { userData } from '@/config/main'
class MXNativeAPI {
  constructor(config = {}) {
    this.cordovaPath = config.cordovaPath

    this.onLoad = this.onEvent('load-success')
    this.emitLoad = this.emitEvent('load-success')
    this.onLoadFail = this.onEvent('load-fail')
    this.emitLoadFail = this.emitEvent('load-fail')

    this.registerApis(defaultAPI)
    this.loadCordova()
  }

  get cordovaPath() {
    return this._cordovaPath || 'cordova.js'
  }

  set cordovaPath(path) {
    this._cordovaPath = path || ''
  }

  static get version() {
    return '2.1.0'
  }

  static get platform() {
    return 'Cordova'
  }

  // 最大尝试加载次数
  static get loadNumberMax() {
    return 3
  }

  // 尝试加载次数
  get loadNumber() {
    return this._loadNumber || 0
  }

  set loadNumber(number) {
    this._loadNumber = number || 0
  }

  /**
   * 加载状态
   * -1 - 加载失败
   * 0 - 加载中
   * 1 - 加载成功
   */
  get loadState() {
    return this._loadState || 0
  }

  set loadState(state) {
    if ([-1, 0, 1].includes(state)) this._loadState = state
    else throw new Error('MXNativeAPI loadState set value illegal')
  }

  get isReady() {
    return this.loadState === 1
  }

  checkCordova() {
    return (
      window.cordova &&
      window.cordova.version &&
      window.cordova instanceof Object
    )
  }

  loadCordova() {
    if (this.checkCordova()) {
      this.loadCordovaSuccess()
      return
    }
    // 超过尝试加载次数则加载失败
    if (this.loadNumber < MXNativeAPI.loadNumberMax) {
      this.loadNumber++
      this.cordovaLoad()
        .then(() => this.loadCordovaSuccess())
        .catch(() => this.loadCordovaError())
    } else this.loadCordovaFail()
  }

  loadCordovaSuccess() {
    console.info('Cordova load success!')
    this.loadState = 1
    this.emitApiQueue()
    this.emitLoad()
  }

  loadCordovaError(error) {
    console.error('Cordova load error! number:', this.loadNumber)
    console.error(error)
    this.loadState = 0
    this.loadCordova()
  }

  loadCordovaFail() {
    console.error('Cordova load fail!')
    this.loadState = -1
    this.emitLoadFail()
  }

  cordovaLoad() {
    const { cordovaPath } = this
    return new Promise((resolve, reject) => {
      try {
        const script = window.document.createElement('script')
        script.src = cordovaPath
        script.type = 'text/javascript'
        script.async = true
        script.onload = () => window.document.addEventListener('deviceready', resolve, false)
        script.onerror = error => reject(error)
        window.document.head.appendChild(script)
      } catch (err) {
        reject(err)
      }
    })
  }

  cordovaExec(success, error, module, name, params = []) {
    if (typeof success !== 'function') throw new Error('MXNativeAPI cordovaExec success not function')
    if (typeof error !== 'function') throw new Error('MXNativeAPI cordovaExec error not function')
    if (!module) throw new Error('MXNativeAPI cordovaExec module is a required')
    if (!name) throw new Error('MXNativeAPI cordovaExec name is a required')
    if (!params) throw new Error('MXNativeAPI cordovaExec params not array')
    window.cordova && window.cordova.exec(success, error, module, name, params)
  }

  get eventQueue() {
    if (!this._eventQueue) this._eventQueue = new Map()
    return this._eventQueue
  }

  onEvent(name) {
    return (function (func) {
      const self = this
      if (typeof func !== 'function') {
        console.warn('MXNativeAPI onEvent', name, '参数不正确')
        return
      }
      let queue = self.eventQueue.get(name)
      if (!queue) {
        queue = new Set()
        self.eventQueue.set(name, queue)
      }
      if (queue.has(func)) return
      queue.add(func)
    }).bind(this)
  }

  emitEvent(name, clear = true) {
    return (function () {
      const self = this
      const queue = self.eventQueue.get(name)
      queue && queue.size && queue.forEach(func => setTimeout(func))
      // clear queue
      if (clear) {
        queue && queue.clear()
        self.eventQueue.delete(name)
      }
    }).bind(this)
  }

  get apiQueue() {
    if (!this._apiQueue) this._apiQueue = new Set()
    return this._apiQueue
  }

  emitApiQueue() {
    const queue = this.apiQueue
    queue.size && queue.forEach(({
      resolve,
      reject,
      module,
      name,
      params
    }) => this.cordovaExec(resolve, reject, module, name, params))
    // clear queue
    queue.clear()
  }

  apiCall(module, name, params = []) {
    return new Promise((resolve, reject) => {
      if (
        // 开发时 + 非美信环境 使用本地用户数据
        process.env.NODE_ENV === 'development' &&
        !device.isMission() &&
        module === 'MideaUser'  && name === 'getUser'
      ) {
        resolve(userData)
        return
      }

      if (!this.isReady) {
        this.apiQueue.add({
          resolve,
          reject,
          module,
          name,
          params
        })
        return
      }

      this.cordovaExec(resolve, reject, module, name, params)
    })
  }

  apiBind(name, module) {
    return (function (...params) {
      const self = this
      if (params.length === 1 && Array.isArray(params[0])) params = params[0]
      return self.apiCall(module, name, params)
    }).bind(this)
  }

  get api() {
    if (!this._api) this._api = {}
    return this._api
  }

  registerApi(module, name) {
    if (!module) {
      console.warn('MXNativeAPI registerApi module is required')
      return
    }
    if (!name) {
      console.warn('MXNativeAPI registerApi name is required')
      return
    }
    if (this.api[name]) {
      console.warn('MXNativeAPI registerApi name is existing;', 'Please use apiCall')
      return
    }
    this.api[name] = this.apiBind(name, module)
  }

  registerApis(apis) {
    apis && apis.length && apis.forEach(api => this.registerApi(...api))
  }
}

export default MXNativeAPI
